package drds.binlog.parse.driver.packets.client;

import drds.binlog.common.Authors;
import drds.binlog.parse.driver.packets.PacketWithHeaderPacket;
import drds.binlog.parse.driver.utils.Bytes;
import drds.binlog.parse.driver.utils.MSC;
import drds.binlog.parse.driver.utils.PasswordEncrypter;
import drds.common.Author;
import org.apache.commons.lang.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;

@Author(name = Authors.LI_YANG)
public class ClientAuthenticationPacket extends PacketWithHeaderPacket
{

    private String username;
    private String password;
    private byte charsetNumber;
    private String databaseName;
    private int serverCapabilities;
    private byte[] scrumbleBuff;

    public void fromBytes(byte[] bytes)
    {
        // bypass since nowhere to use.
    }

    /**
     * <pre>
     * VERSION 4.1
     *  Bytes                        Name
     *  -----                        ----
     *  4                            client_flags
     *  4                            max_packet_size
     *  1                            charset_number
     *  23                           (filler) always 0x00...
     *  n (Null-Terminated String)   user
     *  n (Length Coded Binary)      scramble_buff (1 + x bytes)
     *  n (Null-Terminated String)   databasename (optional)
     * </pre>
     *
     * @throws IOException
     */
    public byte[] toBytes() throws IOException
    {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        // 1. write client_flags
        // 1|4|512|1024|8192|32768
        /**
         * CLIENT_LONG_PASSWORD CLIENT_LONG_FLAG CLIENT_PROTOCOL_41
         * CLIENT_INTERACTIVE CLIENT_TRANSACTIONS CLIENT_SECURE_CONNECTION
         * CLIENT_MULTI_STATEMENTS;
         */
        Bytes.writeUnsigned4IntLittleEndian(byteArrayOutputStream, 1 | 4 | 512 | 8192 | 32768 | 0x00010000); // remove
        // client_interactive
        // feature

        // 2. write max_packet_size
        Bytes.writeUnsigned4IntLittleEndian(byteArrayOutputStream, MSC.max_packet_length);
        // 3. write charset_number
        byteArrayOutputStream.write(this.charsetNumber);
        // 4. write (filler) always 0x00...
        byteArrayOutputStream.write(new byte[23]);
        // 5. write (Null-Terminated String) user
        Bytes.writeNullTerminatedString(byteArrayOutputStream, getUsername());
        // 6. write (Length Coded Binary) scramble_buff (1 + x bytes)
        if (StringUtils.isEmpty(getPassword()))
        {
            byteArrayOutputStream.write(0x00);
        } else
        {
            try
            {
                byte[] encryptedPassword = PasswordEncrypter.scramble411(getPassword().getBytes(), scrumbleBuff);
                Bytes.writeLengthAndLengthCodedBytes(byteArrayOutputStream, encryptedPassword);
            } catch (NoSuchAlgorithmException e)
            {
                throw new RuntimeException("can't encrypt password that will be sent to MySQL server.", e);
            }
        }
        // 7 . (Null-Terminated String) databasename (optional)
        if (getDatabaseName() != null)
        {
            Bytes.writeNullTerminatedString(byteArrayOutputStream, getDatabaseName());
        }
        // end write
        return byteArrayOutputStream.toByteArray();
    }

    public String getUsername()
    {
        return username;
    }

    public void setUsername(String username)
    {
        this.username = username;
    }

    public String getPassword()
    {
        return password;
    }

    public void setPassword(String password)
    {
        this.password = password;
    }

    public byte getCharsetNumber()
    {
        return charsetNumber;
    }

    public void setCharsetNumber(byte charsetNumber)
    {
        this.charsetNumber = charsetNumber;
    }

    public String getDatabaseName()
    {
        return databaseName;
    }

    public void setDatabaseName(String databaseName)
    {
        this.databaseName = databaseName;
    }

    public int getServerCapabilities()
    {
        return serverCapabilities;
    }

    public void setServerCapabilities(int serverCapabilities)
    {
        this.serverCapabilities = serverCapabilities;
    }

    public byte[] getScrumbleBuff()
    {
        return scrumbleBuff;
    }

    public void setScrumbleBuff(byte[] scrumbleBuff)
    {
        this.scrumbleBuff = scrumbleBuff;
    }

}
